package task2;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.logging.Level;
import java.util.logging.Logger;

public class CyclicBuffer<T> {
	
	private final Lock lock = new ReentrantLock();
	private final Condition notFull = lock.newCondition();
	private final Condition notEmpty = lock.newCondition();
	
	private int put;
	private int take;
	private int count;
	private int maxSize;
	
	private final List<T> data;
	
	public CyclicBuffer(int maxSize){
		this.maxSize = maxSize;
		data = new ArrayList<T>(maxSize);
		for(int i = 0; i < maxSize; i++){
			data.add(null);
		}
	}
	
	public void put(T x) throws InterruptedException{
		lock.lock();
		try{
			while(count == maxSize){
				notFull.await();
			}
			data.set(put, x);
			if(++put == maxSize)
				put = 0;
			++count;
			notEmpty.signal();
		} finally {
			lock.unlock();
		}
	}
	
	public T take() throws InterruptedException{
		lock.lock();
		try{
			while(count == 0){
				notEmpty.await();
			}
			T result = data.get(take);
			if(++take == maxSize)
				take = 0;
			--count;
			notFull.signal();
			return result;
		} finally {
			lock.unlock();
		}
	}
	
	public static void main(String[] str){
		
		CyclicBuffer<Integer> buffer = new CyclicBuffer<Integer>(5);
		
		ExecutorService service = Executors.newCachedThreadPool();

		Integer[] data1 = {1, 2, 3, 4, 5, 6, 7, 8};
		Integer[] data2 = {10, 20, 30, 40, 50, 60, 70, 80};
		
		service.execute(new Producer<Integer>(buffer, data1));
		service.execute(new Producer<Integer>(buffer, data2));
		service.execute(new Consumer<Integer>(buffer));
		service.execute(new Consumer<Integer>(buffer));
		service.execute(new Consumer<Integer>(buffer));
		
		service.shutdown();
	}
}

class Producer<T> implements Runnable{
	
	private static final Logger LOGGER = Logger.getLogger(Producer.class.getName());
	
    List<T> data;
    CyclicBuffer<T> buffer;
    
    private static int idInitializer = 0;
    private int id = ++idInitializer;

    public Producer(CyclicBuffer<T> buffer, T[] data){
        this.buffer = buffer;
        this.data = new ArrayList<T>(Arrays.asList(data));
    }

    public void run() {
        for(int i = 0; i < data.size(); i++){
            try {
            	Thread.sleep(300);
            	T item = data.get(i);
				buffer.put(item);
				System.out.println("Producer " + id + " put " + item);
			} catch (InterruptedException e) {
				LOGGER.log(Level.SEVERE, e.getMessage());
			}
        }
    }
}

class Consumer<T> implements Runnable{
	
	private static final Logger LOGGER = Logger.getLogger(Consumer.class.getName());
	
    CyclicBuffer<T> buffer;
    
    private static int idInitializer = 0;
    private int id = ++idInitializer;

    public Consumer(CyclicBuffer<T> buffer){
        this.buffer = buffer;
    }

    public void run() {
        while (true){
            try {
            	Thread.sleep(400);
            	T item = buffer.take();
				System.out.println("Producer " + id + " took " + item);
			} catch (InterruptedException e) {
				LOGGER.log(Level.SEVERE, e.getMessage());
			}
        }
    }
}
